package com.nirvana.generate;

import com.nirvana.config.PluginConfig;
import com.nirvana.convert.CamelCaseConverter;
import com.nirvana.factory.ItemFactory;
import com.nirvana.item.CommonItem;
import com.nirvana.item.DefPropertyItem;
import com.nirvana.type.ItemType;
import com.nirvana.utils.CmdLineUtil;
import com.nirvana.utils.CodeUtil;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

import static com.nirvana.config.PluginConfig.PLUGIN_GENERATE_FLAG;

public class FbsGenerator implements GenerateInterface {
    private static FbsGenerator INSTANCE;
    public static FbsGenerator getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new FbsGenerator();
        }
        return INSTANCE;
    }

    private FbsGenerator() {}

    private final CommonItem commonItem = ItemFactory.getCommonItem(ItemType.DefPropertyItem);

    private void generateInclude(StringBuilder includeContent) {
        includeContent.append("""
                include "common.fbs";
                """);
    }

    private void generateNameSpace(StringBuilder namespaceContent) {
        namespaceContent.append("""
                namespace %s;
                
                """.formatted(PluginConfig.getInstance().flatBuffersDotPackage()));
    }

    private void generateField(DefPropertyItem item, StringBuilder fieldContent) {
        fieldContent.append("""
                    %s:%s;
                """.formatted(
                CamelCaseConverter.camelToSnakeCase(item.getPropertyName()),
                parseProperty(item.getPropertyType())
        ));
    }

    private void generateTable(StringBuilder fbsContent, StringBuilder fieldContent) {
        fbsContent.append("""
                table %sFb {
                %s
                }
                """.formatted(
                commonItem.textField.getText(),
                fieldContent.toString()
        ));
    }

    private void generateTableList(StringBuilder tableListContent) {
        tableListContent.append(""" 
                table %sFbList {
                    list:[%sFb];
                }
                
                """.formatted(
                commonItem.textField.getText(),
                commonItem.textField.getText()
        ));
    }

    private void generateRootType(StringBuilder rootTypeContent) {
        rootTypeContent.append("""
                root_type %sFbList;
                %s
                """.formatted(
                commonItem.textField.getText(),
                PLUGIN_GENERATE_FLAG
        ));
    }

    private boolean isTuple(String propertyName) {
        return propertyName.contains(",");
    }

    Map<String, String> fbsTypeMap = null;
    private String parseProperty(String propertyName) {
        if (propertyName.contains("Map")) return "string";
        if (fbsTypeMap == null) initFbsTypeMap();
        int trimIdx = propertyName.contains("Array") ? 5 : propertyName.contains("List") ? 4 : 0;
        String fbsPropertyName = propertyName.substring(trimIdx);
        if (isTuple(fbsPropertyName)) {
            fbsPropertyName = CodeUtil.getTupleFbName(fbsPropertyName);
            if (trimIdx != 0) return "[Tuple%s]".formatted(fbsPropertyName);
            return "Tuple%s".formatted(fbsPropertyName);
        } else if (trimIdx != 0) {
            fbsPropertyName = fbsPropertyName.substring(1, fbsPropertyName.length() - 1);
            return "[%s]".formatted(fbsTypeMap.getOrDefault(fbsPropertyName, fbsPropertyName + "Fb"));
        }
        return fbsTypeMap.getOrDefault(fbsPropertyName, fbsPropertyName + "Fb");
    }

    private void initFbsTypeMap() {
        fbsTypeMap = new HashMap<>();
        fbsTypeMap.put("Int", "int");
        fbsTypeMap.put("Long", "long");
        fbsTypeMap.put("Float", "float");
        fbsTypeMap.put("Double", "double");
        fbsTypeMap.put("String", "string");
        fbsTypeMap.put("BaseAttr", "string");
        fbsTypeMap.put("Boolean", "bool");
    }

    @Override
    public void generate() {
        StringBuilder fbsContent = new StringBuilder();
        generateInclude(fbsContent);
        generateNameSpace(fbsContent);
        StringBuilder fbsFieldContent = new StringBuilder();
        commonItem.itemList.forEach(item -> {
            if (item instanceof DefPropertyItem defPropertyItem) {
                generateField(defPropertyItem, fbsFieldContent);
            }
        });

        if (PluginConfig.getInstance().extendsGroupDef()) {
            fbsFieldContent.append("""
                        off_group:[int];
                        open_group:[int];
                    """);
        }

        generateTable(fbsContent, fbsFieldContent);
        generateTableList(fbsContent);
        generateRootType(fbsContent);

        String className = commonItem.textField.getText();
        String fileName = CamelCaseConverter.camelToSnakeCase(className.replace("Def", "")) + ".fbs";
        String defBasePath = PluginConfig.getInstance().flatBuffersBasePath();
        Path basePath = Paths.get(defBasePath);
        try {
            if (Files.notExists(basePath)) {
                Files.createDirectories(basePath);
            }
            Path path = Paths.get(defBasePath + "/fbs/" + fileName);
            Files.writeString(path, fbsContent.toString());
            if (PluginConfig.getInstance().reGenerateCommon()) {
                generateFlatBuffers(defBasePath, Paths.get(defBasePath + "/fbs/common.fbs"));
            }
            generateFlatBuffers(defBasePath, path);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void generateFlatBuffers(String defBasePath, Path path) {
        CmdLineUtil.exec("%s\\flatc.exe -o %s --java %s".formatted(
                PluginConfig.getInstance().flatBuffersInstallPath(),
                defBasePath.substring(0, defBasePath.indexOf("com/nirvana")),
                path
        ));
    }
}
